home *** CD-ROM | disk | FTP | other *** search
/ PC World 2007 January / PCWorld_2007-01_cd.bin / v cisle / widget / widget.exe / Smarter_Stickies.widget / Smarter Stickies.widget / Contents / stickies.js < prev   
Text File  |  2006-08-10  |  43KB  |  1,499 lines

  1. /*
  2.  
  3. JavaScript code for Yahoo widget: Smarter Stickies
  4. Copyright ⌐2006 William C Barnes - All Rights Reserved
  5. Intended to be read in and executed by onLoad action in Smarter Stickies.kon
  6.  
  7. Summary of changes:
  8.  
  9.     Version 1.0 -> 1.1
  10.     onLoad actions changed to offer help if first time
  11.     sticky show and create changed to force minimum size and position
  12.         entirely on screen (onScreen() and pack() added)
  13.     poor coding (& possible error) in deleteSticky() corrected
  14.     save() modified to replace illegal characters and notify user
  15.     error handling in restoreStickies() corrected and expanded
  16.         to give users choice of deleting erroneous file
  17.     sticky array may now contain "restore failed" place holders
  18.     visible attribute set with 0 and 1 as opposed to false and true
  19.     order of applying widget preferences corrected
  20.     "Smarter Stickies:\n" prepended to alert text
  21.  
  22.     Version 1.1 -> 1.2
  23.     Default value of Ignore Case in search changed to true.
  24.     Let engine handle sticky position (via undocumented Window constructor)
  25.         calls to onScreen() (1.1 hack) removed (but still used in clone())
  26.     Edit Sticky menu item disabled during alarm and alarm editing.
  27.     Clone sticky added.
  28.     Edit Mode saved and restored.
  29.     onKeyDown changed to onKeyPressed
  30.     Added HotKeys
  31.         focusedID, onFocus(), onBlur() added to implement hotkey delete
  32.             doAlarm() grabs focus whenever alarm sounds
  33.         hide(), hideall(), showAlarmed() modified to support hideShowAll()
  34.     Help added to sticky menu
  35.     padShown added to re-hide pad after search
  36.  
  37.     Version 1.2 -> 1.3
  38.  
  39.     Drag and Drop support added: onStickyDrop(), onPadDrop()
  40.     makeHotKey() modified to retain function when unchanged.
  41.  
  42. Issues:
  43.     Use of undocumented constructor: Window(name)
  44.     Switching to or from Window level "Show Only in Heads Up Display" disables hotkeys
  45.     No new windows in heads up display.
  46. */
  47.  
  48.  
  49. //////////////////////  BEGIN ONLOAD ACTIONS /////////////////////////
  50.  
  51.     const BORDER = 6;
  52.     const MIN_WIDTH = 30+2*BORDER;
  53.     const MIN_HEIGHT = 10+2*BORDER;
  54.  
  55. var sticky;        // global array of Sticky objects by their id(s)
  56. var pending;        // head of linked list of stickies with pending alarms
  57. var stickycount;    // # stickies
  58. var focusedID;        // id of focused sticky (null if none)
  59. var alarmTimer;
  60. var helpFile;
  61. var padImage;
  62. var padShown;
  63.  
  64. var newKey;        // Hotkeys
  65. var searchKey;
  66. var hideShowKey;
  67. var deleteKey;
  68.  
  69. initialize();
  70.  
  71. //for (var a in preferences) {
  72. //    print("preferences."+a+".value="+eval("preferences."+a+".value"));
  73. //    }
  74.  
  75. //////////////////////  END OF ONLOAD ACTIONS /////////////////////////
  76.  
  77. function initialize() {
  78.  
  79.     if (preferences.disclaimerAccept.value != "yes") {
  80.  
  81.         var disclaimer = "DISCLAIMER AND USER AGREEMENT\n\nYour use of the Smarter Stickies Widget (the software) is at your own discretion and risk and you will be solely responsible for any damage that results from its use, including, but not limited to, any damage to any computer system or loss of data. The software is provided \"as is\" without warranties of any kind. The author makes no representations or warranties regarding the condition or functionality of the software or its suitability for use. To the maximum extent permitted by applicable law, you hereby release and waive all claims against the author, his agents and representatives from any and all liability for claims, damages (actual and/or consequential), costs and expenses (including litigation costs and attorneys' fees) of every kind and nature, arising from or in any way related to your use of the software.\n\nHave a nice day!";
  82.  
  83.         if (alert(disclaimer,"Close Widget","I Agree") != 2) {
  84.             closeWidget();
  85.             return;
  86.         }
  87.     }
  88.  
  89.     stickycount = 0;        // # stickies
  90.     alarmTimer = new Timer();
  91.  
  92.     helpFile = widget.extractFile("help.html");
  93.     widget.extractFile("smartPad.gif");
  94.     widget.extractFile("almEdit.gif");
  95.  
  96.     // stickyPad is the widget's "main" window defined in Smarter Stickies.kon
  97.     stickyPad.width = 76;
  98.     stickyPad.height = 52;
  99.     stickyPad.onMultiClick = "new Sticky();";
  100.     stickyPad.onContextMenu = "padContextMenu();";
  101.     stickyPad.onGainFocus = "padShown = padImage.visible = 1;"
  102.     padImage = new Image();
  103.     padImage.src = "grayPad.png";
  104.     padImage.colorize = preferences.defaultColorPad.value-0?
  105.         preferences.noteColor.value : preferences.padColor.value;
  106.     padImage.window = stickyPad;
  107.     padImage.onDragDrop = "onPadDrop();";
  108.  
  109.     if (preferences.disclaimerAccept.value != "yes") {
  110.  
  111.         var offerHelp = "Thank you.  Would you like to see the Smarter Stickies help file now?\n(It's available anytime by right-clicking on the pad icon and selecting help from the menu.)";
  112.  
  113.         if (alert(offerHelp, "Yes", "Not now") == 1) runCommand("'"+helpFile+"'");
  114.  
  115.         preferences.disclaimerAccept.value = "yes";
  116.     }
  117.  
  118.     restoreStickies();    // show pad while restoring
  119.      applyPreferences();    // assign hotkeys
  120.  
  121.     stickyPad.focus();
  122.     padShown = stickyPad.visible = preferences.hidePadOnStartup.value-0? 0: 1;
  123. }
  124.  
  125.  
  126. function Sticky(recalled) {
  127.  
  128.     var id;
  129.     if (recalled == null || !recalled.id) { for (id=1; sticky[id]!=null; id++) {} }
  130.     else { id = recalled.id; }
  131.     //1*/print((recalled?(recalled.id?"Restoring":"Cloning"):"Creating")+" sticky["+id+"]");
  132.     this.id = id;
  133.     sticky[id] = this;
  134.  
  135.     stickycount++;
  136.     
  137.     this.window = new Window("sticky"+id);    // UNDOCUMENTED CONSTRUCTOR!!!
  138.         /*
  139.          * I guessed at this constructor and it worked, but I wish it were documented.
  140.          * Sets window.name (a read-only attribute) so that the engine will handle
  141.          * positioning of sticky (which I am MORE than willing to allow - just couldn't
  142.          * figure out how to do it for version 1.0).  Only downside (assuming it works)
  143.          * is extra entries in the registry (after id deleted, before reuse).
  144.          */
  145.     this.matte = new TextArea();
  146.     this.edit = new TextArea();
  147.     if (recalled) {
  148.         this.showAtStartup = recalled.showAtStartup;
  149.         this.alarm = recalled.alarm;
  150.         this.window.width = recalled.width;
  151.         this.window.height = recalled.height;
  152.         if (!recalled.id) {
  153.             this.window.hOffset = recalled.hOffset;
  154.             this.window.vOffset = recalled.vOffset;
  155.         }
  156.         this.window.visible = this.showAtStartup? true: recalled.visible;
  157.         this.font = recalled.font;
  158.         this.edit.size = recalled.size
  159.         this.edit.color = recalled.textColor;
  160.         this.edit.bgColor = recalled.noteColor;
  161.         this.edit.data = recalled.data;
  162.         this.borderColor = recalled.borderColor;
  163.         this.activeColor = recalled.activeColor;
  164.     } else {
  165.         this.showAtStartup = false;
  166.         this.alarm = 0;
  167.         if (preferences.noteHoffset.value != "") {
  168.             this.window.hOffset = preferences.noteHoffset.value;
  169.             this.window.vOffset = preferences.noteVoffset.value;
  170.         }
  171.         this.window.width = preferences.noteWidth.value;
  172.         this.window.height = preferences.noteHeight.value;
  173.         this.font = preferences.font.value;
  174.         this.edit.size = preferences.textSize.value;
  175.         this.edit.color = preferences.textColor.value;
  176.         this.edit.bgColor = preferences.noteColor.value;
  177.         this.borderColor = preferences.borderColor.value;
  178.         this.activeColor = preferences.activeColor.value;
  179.     }
  180.     
  181.     this.window.title="Smarter Stickies";
  182.     this.window.onMultiClick = "toggleEdit("+id+");";
  183.     this.window.onContextMenu = "noteContextMenu("+id+");";
  184.     this.window.onGainFocus = "onFocus("+id+");";
  185.     this.window.onLoseFocus = "onBlur("+id+");";
  186.     
  187.     this.frame = new Frame();
  188.     this.frame.window = this.window;
  189.  
  190.     /* I first tried using a text object for matte
  191.        but found that I couldn't set the height properly */
  192.     /* problem isn't the height but rather that vOffset works
  193.        from the baseline of the text - and no vAlignment */
  194.     this.matte.editable = false;
  195.     this.matte.opacity = 255;
  196.     this.matte.bgColor = this.borderColor;    // by default - not editmode
  197.     this.matte.bgOpacity = 255;
  198.     this.frame.addSubview(this.matte);
  199.     
  200.     this.drag = new Image();
  201.     pack(this);
  202. //    onScreen(this);
  203.     
  204.     this.edit.editable = false;        // by default - not editmode
  205.     this.edit.font = this.font;
  206.     this.edit.opacity = 255;
  207.     this.edit.bgOpacity = 255;
  208.     this.edit.onKeyPress = "keyPress("+id+");";
  209.     this.edit.onDragDrop = "onStickyDrop("+id+");";
  210.     this.frame.addSubview(this.edit);
  211.     
  212.     this.drag.src = "drag.png";
  213.     this.drag.opacity = 0;            // by default - not editmode
  214.     this.drag.tooltip = "Drag to resize sticky"
  215.     this.drag.onMouseDown =
  216.         "sticky["+id+"].drag.onMouseMove = 'resize("+id+");';";
  217.     this.drag.onMouseUp = "sticky["+id+"].drag.onMouseMove = null;"
  218.     this.frame.addSubview(this.drag);
  219.     
  220.     this.nextpending = null;
  221.     if (this.alarm) queueAlarm(this);
  222.     
  223.     if (!recalled) {            // new default sticky
  224.         toggleEdit(id);                // sets editable and changed
  225.     } else if (!recalled.id) {        // new cloned sticky
  226.         toggleEdit(id);                // sets editable and changed
  227.         this.edit.select(0,-1);            // selects all of cloned text
  228.     } else if (recalled.editmode) {     // recalled in edit mode    
  229.         toggleEdit(id);                // sets editable
  230.         this.changed = false;            // but not changed
  231.     } else {                // recalled - not in edit mode
  232.         this.changed = false;            // not changed
  233.     }
  234. }
  235.  
  236. function pack(s) {
  237.  
  238.     s.matte.width = s.frame.width = s.window.width;
  239.     s.matte.height = s.frame.height = s.window.height;
  240.     s.edit.width = s.frame.width - 2*BORDER;
  241.     s.edit.height = s.frame.height - 2*BORDER;
  242.     s.edit.hOffset = BORDER;
  243.     s.edit.vOffset = BORDER;
  244.     var dragsize = BORDER<10? 10: BORDER;
  245.     s.drag.width = dragsize;
  246.     s.drag.height = dragsize;
  247.     s.drag.hOffset = s.frame.width - dragsize;
  248.     s.drag.vOffset = s.frame.height - dragsize;
  249. }
  250.  
  251.  
  252. function keyPress(id) {
  253.  
  254.     var s = sticky[id];
  255.     
  256.     if (s.edit.editable) s.changed = true;
  257. }
  258.     
  259. function toggleEdit(id) {
  260.  
  261.     var s = sticky[id];
  262.  
  263.     if (s.edit.editable) {
  264.         s.edit.editable = false;
  265.         s.matte.bgColor = s.borderColor;
  266.         s.drag.opacity = 0;
  267.         s.window.locked = preferences.konfabulatorLockWindowPosition.value;
  268.         s.window.opacity = preferences.konfabulatorWindowOpacity.value;
  269.     } else {
  270.         s.edit.editable = true;
  271.         s.matte.bgColor = s.activeColor;
  272.         s.drag.opacity = 255;
  273.         s.window.opacity = 255;
  274.         s.window.locked = false;
  275.         s.window.focus();
  276.         s.edit.focus();
  277.     }
  278.     s.changed = true;
  279. }
  280.  
  281. function resize(id) {
  282.  
  283.     var s = sticky[id];
  284.  
  285.     if (s.window.width + system.event.x >= MIN_WIDTH) {
  286.         s.window.width += system.event.x;
  287.         s.frame.width += system.event.x;
  288.         s.matte.width += system.event.x;
  289.         s.edit.width += system.event.x;
  290.         s.drag.hOffset += system.event.x;
  291.     }    
  292.     if (s.window.height + system.event.y >= MIN_HEIGHT) {
  293.         s.window.height += system.event.y;
  294.         s.frame.height += system.event.y;
  295.         s.matte.height += system.event.y;
  296.         s.edit.height += system.event.y;
  297.         s.drag.vOffset += system.event.y;
  298.     }    
  299.     s.changed = true;
  300. }
  301.  
  302. function noteContextMenu(id) {
  303.  
  304.     var s = sticky[id];
  305.     var items = new Array();
  306.     
  307.     items[0] = new MenuItem();
  308.     items[0].title = "Sticky Preferences...";
  309.     items[0].enabled = (s.alarmEditor||s.timer)? false : true;
  310.     items[0].onSelect = "stickyPref("+id+");";
  311.  
  312.     items[1] = new MenuItem();
  313.     items[1].title = "Clone Sticky";
  314.     items[1].enabled = (s.alarmEditor||s.timer)? false : true;
  315.     items[1].onSelect = "clone("+id+");";
  316.  
  317.     items[2] = new MenuItem();
  318.     items[2].title = "Edit Sticky";
  319.     items[2].enabled = (s.alarmEditor||s.timer)? false : true;
  320.     items[2].checked = s.edit.editable? true : false ;
  321.     items[2].onSelect = "toggleEdit("+id+");";
  322.  
  323.     items[3] = new MenuItem();
  324.     if (s.timer) {
  325.         items[3].title = "Stop Alarm: "+ new Date(s.alarm).toLocaleString();
  326.         items[3].onSelect = "endAlarm("+id+");";
  327.     } else {
  328.         items[3].title = s.alarm?
  329.             "Edit Alarm: "+ new Date(s.alarm).toLocaleString()
  330.             : "Set Alarm";
  331.         items[3].onSelect = "new AlarmEditor("+id+");";
  332.     items[3].enabled = (s.alarmEditor)? false : true;
  333.     }
  334.  
  335.     items[4] = new MenuItem();
  336.     items[4].title = "Hide Sticky";
  337.     items[4].enabled = (s.alarmEditor||s.timer)? false : true;
  338.     items[4].onSelect = "hide("+id+");";
  339.  
  340.     items[5] = new MenuItem();
  341.     items[5].title = "Show at Start & Wake";
  342.     items[5].checked = s.showAtStartup;
  343.     items[5].onSelect = "toggleShowAtStartup("+id+");";
  344.  
  345.     items[6] = new MenuItem();
  346.     items[6].title = "Delete Sticky";
  347.     items[6].onSelect = "deleteSticky("+id+");";
  348.  
  349.     items[7] = new MenuItem();
  350.     items[7].title = "Help";
  351.     items[7].onSelect = "runCommand(\"'"+helpFile+"'\");";
  352.  
  353.     s.window.contextMenuItems = items;
  354. }
  355.  
  356. function toggleShowAtStartup(id) {
  357.  
  358.     var s = sticky[id];
  359.     
  360.     s.showAtStartup = !s.showAtStartup;
  361.     s.changed = true;
  362. }
  363.  
  364. function filename(id) {
  365.  
  366.     return system.widgetDataFolder+"/sticky("+id+").xml";
  367. }
  368.  
  369. function save(id) {
  370.  
  371.     var s = sticky[id];
  372.     
  373.     if (!s.changed && s.window.hOffset==s.savedX
  374.             && s.window.vOffset==s.savedY) return;
  375.     
  376.     var doc = XMLDOM.createDocument();
  377.     var note = doc.createElement( "note" );
  378.     doc.appendChild(note);
  379.     
  380.     note.setAttribute("width",s.window.width);
  381.     note.setAttribute("height",s.window.height);
  382.     note.setAttribute("visible",s.window.visible);
  383.     note.setAttribute("showAtStartup",s.showAtStartup);
  384.     note.setAttribute("alarm",s.alarm);
  385.     note.setAttribute("font",s.font);    // cannot use s.edit.font!
  386.     note.setAttribute("size",s.edit.size);
  387.     note.setAttribute("textColor",s.edit.color);
  388.     note.setAttribute("noteColor",s.edit.bgColor);
  389.     note.setAttribute("borderColor",s.borderColor);
  390.     note.setAttribute("activeColor",s.activeColor);
  391.     note.setAttribute("editmode",s.edit.editable);
  392.     
  393.     // get rid of any illegal characters in the text 
  394.     //s.edit.data = s.edit.data.replace(/([\001-\010\013\014\016-\037])/g," ");
  395.     if (!arguments.callee.illegalRE) {
  396.         arguments.callee.illegalRE = new RegExp('[\001-\010\013\014\016-\037]','g');
  397.     }
  398.     if (arguments.callee.illegalRE.test(s.edit.data)) {
  399.         s.edit.data = s.edit.data.replace(arguments.callee.illegalRE," ");
  400.         alert("Smarter Stickies:\nOne or more illegal characters in the last sticky have been replaced with space(s).");
  401.         // replace VT(0xb) and FF(0xc) with newline?
  402.     }    
  403.  
  404.     var start = 0;
  405.     var end;
  406.     do {
  407.         // must split any instances of "]]>" (the CDATA terminator) in
  408.         // the text so that the XML parser doesn't choke
  409.         end = s.edit.data.indexOf("]]>",start);
  410.         if (end >= 0) end++;        // split ]]> between ] and ]>
  411.         note.appendChild(doc.createCDATASection(
  412.             s.edit.data.slice(start,end>=0?end:s.edit.data.length)));
  413.         start = end;
  414.     } while (end >= 0);
  415.  
  416.     filesystem.writeFile(filename(id),doc.toXML());
  417.  
  418.     s.changed = false;
  419. }
  420.  
  421. function saveall() {
  422.  
  423.     for (var id=1; id<sticky.length; id++) {
  424.         if (sticky[id]!=null && sticky[id].window) save(id);
  425.     }
  426. }
  427.  
  428. function restoreStickies() {
  429.  
  430.     sticky = new Array();
  431.  
  432.     var file = filesystem.getDirectoryContents(system.widgetDataFolder,false);
  433.     for (var i=0; i<file.length; i++) {
  434.  
  435.         var recalled = new Object();
  436.         if (
  437.             file[i].length > 12 &&
  438.             file[i].substr(0,7) == "sticky(" &&
  439.             file[i].substr(-5) == ").xml" &&
  440.             (recalled.id = file[i].slice(7,-5) - 0) > 0 &&
  441.             sticky[recalled.id] == null
  442.         ) {
  443.         try {
  444.             var dom = XMLDOM.parse(filesystem.readFile(filename(recalled.id)));
  445.             var nlst = dom.getElementsByName("note");
  446.             if (nlst.length!=1) {
  447.                 print("dom contains "+nlst.length+" notes?");
  448.                 throw RangeError;
  449.             }
  450.             var xnote = nlst.item(0);
  451.  
  452.             recalled.width = xnote.getAttribute("width") - 0;
  453.             recalled.height = xnote.getAttribute("height") - 0;
  454.             recalled.visible = xnote.getAttribute("visible") - 0;
  455.             recalled.showAtStartup = xnote.getAttribute("showAtStartup")=="true";
  456.             recalled.alarm = xnote.getAttribute("alarm") - 0;
  457.             recalled.font = xnote.getAttribute("font");
  458.             recalled.size = xnote.getAttribute("size");
  459.             recalled.textColor = xnote.getAttribute("textColor");
  460.             recalled.noteColor = xnote.getAttribute("noteColor");
  461.             recalled.borderColor = xnote.getAttribute("borderColor");
  462.             recalled.activeColor = xnote.getAttribute("activeColor");
  463.             recalled.editmode = xnote.getAttribute("editmode")=="true";
  464.  
  465.             var cdnlst = xnote.childNodes;
  466.             var j = 0;
  467.             recalled.data = "";
  468.             while (j < cdnlst.length) {
  469.                 recalled.data += cdnlst.item(j++).data;
  470.             }
  471.             new Sticky(recalled);
  472.         } catch (e) {
  473.             var resp = alert("Smarter Stickies:\nCould not load sticky.  File may be corrupted.\n"+
  474.                 filename(recalled.id) + "\n" + e, "Delete","Skip");
  475.             if (resp == 1) {
  476.                 filesystem.moveToRecycleBin(filename(recalled.id));
  477.             } else {
  478.                 sticky[recalled.id] = "restore failed";
  479.             }
  480.         }
  481.         }
  482.     }
  483. }
  484.  
  485. function show(id) {
  486.  
  487.     var s = sticky[id];
  488.     if (!s.window.visible) {
  489.         s.window.visible = 1;
  490.         s.changed = true;
  491.     }
  492.     onScreen(s);
  493.     s.window.level = "topmost";
  494.     s.window.focus();
  495.     s.window.level = preferences.konfabulatorWindowLevel.value;
  496.     s.window.opacity = preferences.konfabulatorWindowOpacity.value;
  497. }
  498.  
  499. function showAlarmed(state) {
  500.  
  501.     for (var id=0; id<sticky.length; id++) {
  502.         if (sticky[id] == null || !sticky[id].window) continue;
  503.         if (state === undefined) show(id);
  504.         else if (!sticky[id].alarm ^ state) show(id);
  505.     }
  506. }
  507.  
  508. function hide(id) {
  509.  
  510.     var s = sticky[id];
  511.     if (s.timer) return;    // Don't hide while alarm is active!
  512.     var result = false;
  513.     if (s.window.visible) {
  514.         s.window.visible = 0;
  515.         s.changed = true;
  516.         result = true;
  517.     }
  518.     if (focusedID == id) focusedID = null;
  519.     return result;
  520. }
  521.  
  522. function hideall() {
  523.  
  524.     var result = false
  525.     for (var id=0; id<sticky.length; id++) {
  526.         if (sticky[id] == null || !sticky[id].window) continue;
  527.         result |= hide(id);
  528.     }
  529.     return result;
  530. }
  531.  
  532. function hideShowAll() {
  533.  
  534.     if (!hideall()) showAlarmed();
  535. }
  536.  
  537. function padContextMenu() {
  538.  
  539.     var items = new Array();
  540.     
  541.     items[0] = new MenuItem();
  542.     items[0].title = "New Sticky  ("+stickycount+" exist)";
  543.     items[0].onSelect = "new Sticky();";
  544.  
  545.     items[1] = new MenuItem();
  546.     items[1].title = "Search Stickies...";
  547.     items[1].onSelect = "search();";
  548.  
  549.     items[2] = new MenuItem();
  550.     items[2].title = "Show Alarmed";
  551.     items[2].onSelect = "showAlarmed(true);";
  552.  
  553.     items[3] = new MenuItem();
  554.     items[3].title = "Show Unalarmed";
  555.     items[3].onSelect = "showAlarmed(false);";
  556.  
  557.     items[4] = new MenuItem();
  558.     items[4].title = "Hide All Stickies";
  559.     items[4].onSelect = "hideall();";
  560.  
  561.     items[5] = new MenuItem();
  562.     items[5].title = "Hide Pad";
  563.     items[5].onSelect = "padShown = stickyPad.visible = 0;";
  564.  
  565.     items[6] = new MenuItem();
  566.     items[6].title = "Help";
  567.     items[6].onSelect = "runCommand(\"'"+helpFile+"'\");";
  568.  
  569.     stickyPad.contextMenuItems = items;    
  570. }
  571.  
  572. function queueAlarm(stky) {
  573.     if (!pending || pending.alarm>=stky.alarm) {
  574.         stky.nextpending = pending;
  575.         pending = stky;
  576.     }
  577.     else {
  578.         var p = pending;
  579.         while (p.nextpending && p.nextpending.alarm<stky.alarm) {
  580.             p = p.nextpending;
  581.         }
  582.         stky.nextpending = p.nextpending;
  583.         p.nextpending = stky;
  584.     }
  585.     checkAlarms();
  586. }    
  587.  
  588. function pq() { // for debug only
  589.     print("alarm queue:\n");
  590.     for (var s = pending; s; s=s.nextpending) {
  591.         print("   ID = "+s.id);
  592.         print("  alm = "+new Date(s.alarm));
  593.         print("");
  594.     }
  595. }
  596.  
  597. function checkAlarms() {
  598.  
  599.     alarmTimer.ticking = false;
  600.     
  601.     var now = new Date().getTime();
  602.     
  603.     var s;
  604.     while ((s = pending) && s.alarm<=now) {
  605.         pending = s.nextpending;
  606.         s.nextpending = null;
  607.         doAlarm(s.id);
  608.     }
  609.     if (pending) {
  610.         alarmTimer.onTimerFired = "checkAlarms();";
  611.         alarmTimer.interval = (pending.alarm - now)/1000;
  612.         alarmTimer.ticking = true;
  613.     }
  614. }
  615.  
  616. function doAlarm(id) {
  617.  
  618.     var s = sticky[id];
  619.  
  620.     if (!s.timer) {  // first time
  621.         s.window.onMultiClick = "endAlarm("+id+");";
  622.         if (s.edit.editable) toggleEdit(id);
  623.         s.timer = new Timer();
  624.         s.timer.interval = 0.5;
  625.         s.timer.onTimerFired = "doAlarm("+id+");";
  626.         s.timer.ticking = true;
  627.         s.timercount = 0;
  628.         s.window.visible = 1;
  629.         s.window.level = "topmost";
  630.         s.window.opacity = 255;
  631.         onScreen(s);
  632.     }
  633.     if (!s.timercount) {
  634.         s.window.focus();
  635.         play(preferences.almSound.value);
  636.     }
  637.     s.matte.bgColor = s.timercount%2 ? s.borderColor : s.activeColor;
  638.     if (++s.timercount >= 30) s.timercount = 0;
  639. }
  640.  
  641. function endAlarm(id) {
  642.  
  643.     var s = sticky[id];
  644.  
  645.     s.matte.bgColor = s.borderColor;
  646.     s.window.level = preferences.konfabulatorWindowLevel.value;
  647.     s.window.opacity = preferences.konfabulatorWindowOpacity.value;
  648.     s.window.onMultiClick = "toggleEdit("+id+");";
  649.     s.timer.ticking = false;
  650.     s.timer = null;
  651.     s.timercount = null;
  652.     s.alarm = 0;            // shouldn't happen until acked
  653.     s.changed = true;
  654. }
  655.  
  656. function AlarmEditor(id) {
  657.  
  658.     const ALMEDIT_WIDTH = 200;
  659.     const ALMEDIT_HEIGHT = 64;
  660.  
  661.     var s = sticky[id];
  662.     if (s.timer) return;    // alarm triggered while menu was up!
  663.     
  664.     var oldalm = s.alarm;
  665.     unsetAlarm(s);
  666.     
  667.     s.alarmEditor = this;
  668.     
  669.     if (s.edit.editable) {
  670.         toggleEdit(id);
  671.         this.wasEditing = true;
  672.     }
  673.     s.window.onMultiClick = "beep();";
  674.     
  675.     this.frame = new Frame();
  676.     s.window.root.addSubview(this.frame);
  677.  
  678.     this.frame.width = ALMEDIT_WIDTH;
  679.     if (s.window.width < this.frame.width) {
  680.         var hdiff = (this.frame.width - s.window.width)/2;
  681.         s.window.width = this.frame.width;
  682.         s.window.hOffset -= hdiff;
  683.         s.frame.hOffset = hdiff;
  684.     } else {
  685.         this.frame.hOffset = (s.window.width - this.frame.width)/2;
  686.     }
  687.     this.frame.height = ALMEDIT_HEIGHT;
  688.     if (s.window.height < this.frame.height) {
  689.         var vdiff = (this.frame.height - s.window.height)/2;
  690.         s.window.height = this.frame.height;
  691.         s.window.vOffset -= vdiff;
  692.         s.frame.vOffset = vdiff;
  693.     } else {
  694.         this.frame.vOffset = (s.window.height - this.frame.height)/2;
  695.     }
  696.     
  697.     this.background = new TextArea();
  698.     this.background.editable = false;
  699.     this.background.width = this.frame.width;
  700.     this.background.height = this.frame.height;
  701.     this.background.bgColor = "#404040";
  702.     this.background.bgOpacity = 191;
  703.     this.frame.addSubview(this.background);
  704.  
  705.     this.prmt = new Text();
  706.     this.prmt.hOffset = 8;
  707.     this.prmt.vOffset = 18;        // with respect to text BASELINE!!!
  708.     this.prmt.color = "#FFFFFF";
  709.     this.prmt.size = 14;
  710.     this.prmt.data = "Enter alarm date and/or time:";
  711.     this.frame.addSubview(this.prmt);
  712.  
  713.     this.input = new TextArea();
  714.     this.input.hOffset = 16;
  715.     this.input.vOffset = 24;    // with respect to text BASELINE!!!
  716.     this.input.width = this.frame.width - 2*this.input.hOffset;
  717.     this.input.height = 20;
  718.     this.input.size = 15;
  719.     this.input.style = "Bold";
  720.     this.input.bgColor = "#D0D0D0";
  721.     this.input.bgOpacity = 255;
  722.     this.input.onKeyPress = "inputAlarm("+id+");";
  723.     this.input.data = alm2str(oldalm);
  724.     this.frame.addSubview(this.input);
  725.     
  726.     this.inputsaved = null;
  727.     
  728.     this.cancel = new Text();
  729.     this.cancel.hOffset = 16;
  730.     this.cancel.vOffset = ALMEDIT_HEIGHT - 7;
  731.     this.cancel.bgColor = "#000000";
  732.     this.cancel.bgOpacity = 255;
  733.     this.cancel.color = "#FFFFFF";
  734.     this.cancel.size = 10;
  735.     this.cancel.style = "Bold";
  736.     this.cancel.data = "   UNSET  ";
  737.     this.cancel.onMouseDown = "inputAlarm("+id+",1);";
  738.     this.frame.addSubview(this.cancel);
  739.  
  740.     this.clear = new Text();
  741.     this.clear.hOffset = 81;
  742.     this.clear.vOffset = ALMEDIT_HEIGHT - 7;
  743.     this.clear.bgColor = "#000000";
  744.     this.clear.bgOpacity = 255;
  745.     this.clear.color = "#FFFFFF";
  746.     this.clear.size = 10;
  747.     this.clear.style = "Bold";
  748.     this.clear.data = "  CLEAR ";
  749.     this.clear.onMouseDown = "inputAlarm("+id+",2);";
  750.     this.frame.addSubview(this.clear);
  751.  
  752.     this.enter = new Text();
  753.     this.enter.hOffset = 139;
  754.     this.enter.vOffset = ALMEDIT_HEIGHT - 7;
  755.     this.enter.bgColor = "#000000";
  756.     this.enter.bgOpacity = 255;
  757.     this.enter.color = "#FFFFFF";
  758.     this.enter.size = 10;
  759.     this.enter.style = "Bold";
  760.     this.enter.onMouseDown = "inputAlarm("+id+",3);";
  761.     this.enter.data = "  ENTER ";
  762.     this.frame.addSubview(this.enter);
  763.  
  764.  
  765.     this.input.select(-1,-1);
  766.     this.input.focus();
  767. }
  768.  
  769. function alm2str(alarmtime) {
  770.  
  771.     if (!alarmtime) return "";
  772.  
  773.     var d = new Date(alarmtime);
  774.     
  775.     var hours = ""+d.getHours();
  776.     if (hours.length==1) hours = "0"+hours;
  777.     var minutes = ""+d.getMinutes();
  778.     if (minutes.length==1) minutes = "0"+minutes;
  779.     var seconds = ""+d.getSeconds();
  780.     if (seconds.length==1) seconds = "0"+seconds;
  781.     
  782.     return ""+(d.getMonth()+1)+"/"+d.getDate()+"/"+d.getFullYear() + " "
  783.         +hours+":"+minutes+":"+seconds;
  784. }
  785.  
  786. function inputAlarm(id,button) { // button: unset/cancel=1, clear=2, enter=3
  787.  
  788.     // hacking in the buttons has made this somewhat messy
  789.  
  790.     var s = sticky[id];
  791.     var input = s.alarmEditor.input;
  792.     
  793.     if (button == 2) {    // clear input
  794.         input.data = "";
  795.         input.focus();
  796.         input.select(-1,-1);
  797.         return;
  798.     }
  799.  
  800.     var k = -1;
  801.     if (!button &&
  802.       (k = "0123456789/: \t\r\n\bdhmsapDHMSAP".indexOf(system.event.key)) < 0) {
  803.         // illegal key pressed
  804.         input.rejectKeyPress();
  805.         beep();
  806.         return;
  807.     }
  808.     if ( button || k == 13 || k == 14 || k == 15 ) {
  809.         var alm;
  810.         // Tab, Return, or Newline pressed or button clicked
  811.         if (button == 1) {    // unset/cancel button clicked
  812.             alm = 0;
  813.             s.alarmEditor.inputsaved = null;
  814.         } else {
  815.             if (!button) input.rejectKeyPress();
  816.             // parse input string
  817.             var adate = str2date(input.data);
  818. //            var adate = new Date(input.data);
  819.             alm = adate.getTime();
  820.             if (k == 13) {
  821.                 // tab entered
  822.                 if (!s.alarmEditor.inputsaved) {
  823.                     // display parsed date
  824.                     s.alarmEditor.inputsaved = input.data;
  825.                     input.data = alm2str(alm);
  826.                 } else {
  827.                     // restore unparsed data
  828.                     input.data = s.alarmEditor.inputsaved;
  829.                     s.alarmEditor.inputsaved = null;
  830.                 }
  831.                 input.select(-1,-1);
  832.                 return;
  833.             }
  834.             // return or newline - try to set alarm and exit
  835.             if (    !alm && input.data.length
  836.                     // input must be empty to cancel alarm
  837.                 || alm && (alm + 1000 < new Date().getTime())
  838.                     // alarm must be at least 1 sec in the future
  839.             ) {
  840.                 beep();
  841.                 return;
  842.             }
  843.         }
  844.         // restore window
  845.         s.alarmEditor.frame.removeFromSuperview();
  846.         if (s.alarmEditor.wasEditing) toggleEdit(id);
  847.         s.alarmEditor = null;
  848.         if (s.frame.hOffset) {
  849.             s.window.hOffset += s.frame.hOffset;
  850.             s.window.width = s.frame.width;
  851.             s.frame.hOffset = 0;
  852.         }
  853.         if (s.frame.vOffset) {
  854.             s.window.vOffset += s.frame.vOffset;
  855.             s.window.height = s.frame.height;
  856.             s.frame.vOffset = 0;
  857.         }
  858.         s.window.onMultiClick = "toggleEdit("+id+");";
  859.         // set alarm
  860.         s.alarm = alm;
  861.         if (alm) queueAlarm(s);
  862.         s.changed = true;
  863.         return;
  864.     }
  865.     if (input.data.length >= 21) {
  866.         input.rejectKeyPress();
  867.         beep();
  868.     } else {
  869.         // "normal" key pressed
  870.         s.alarmEditor.inputsaved = null;
  871.     }
  872. }
  873.  
  874. function str2date(str) {
  875.  
  876.     //3*/print("Parsing...");
  877.     var bad = new Date("x");
  878.     if (!str || str=="") {
  879.         //3*/print("null or null string");
  880.         return bad;
  881.     }
  882.     var month;
  883.     var day;
  884.     var year;
  885.     var yearSpecified = false;
  886.     var hours;
  887.     var minutes;
  888.     var seconds;
  889.     var am = false;
  890.     var pm = false;
  891.  
  892.     var now = new Date();
  893.  
  894.     str = str.toLowerCase();
  895.     if (/am/.test(str)) {
  896.         am = true;
  897.         str = str.replace(/am/," ");
  898.     }
  899.     if (/pm/.test(str)) {
  900.         pm = true;
  901.         str = str.replace(/pm/," ");
  902.     }
  903.     if (am && pm) {
  904.         //3*/print("am and pm");
  905.         return bad;
  906.     }
  907.  
  908.     if (!am && !pm && /[dhms]/.test(str)) {
  909.         var nowplus = str.match(/^\s*(?:(\d+)d)?\s*(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?\s*$/);
  910.         if (!nowplus) {
  911.             //3*/print("nowplus: failed");
  912.             return bad
  913.         }
  914.         if (isNaN(day = nowplus[1]-0)) day = 0;
  915.         if (isNaN(hours = nowplus[2]-0)) hours = 0;
  916.         if (isNaN(minutes = nowplus[3]-0)) minutes = 0;
  917.         if (isNaN(seconds = nowplus[4]-0)) seconds = 0;
  918.         //3*/print("nowplus: "+day+"d "+hours+"h "+minutes+"m "+seconds+"s");
  919.         if (day+hours+minutes+seconds==0) {
  920.             //3*/print("nowplus = 0");
  921.             return bad;
  922.         }
  923.         var millis = day * 24 + hours;
  924.         millis = millis * 60 + minutes;
  925.         millis = millis * 60 + seconds;
  926.         millis = millis * 1000;
  927.         now.setTime(now.getTime()+millis);
  928.         //3*/print("nowplus: OK");
  929.         return now;
  930.     }
  931.     
  932.     if (/[ap]/.test(str)) {
  933.         //3*/print("leftover a or p");
  934.         return bad;
  935.     }
  936.     
  937.     // to make searching for bare numbers easier
  938.     str = " "+str.replace(/ /g,"  ")+" ";
  939.     // must be done before mdy is determined
  940.  
  941.     // determine # slashes in input
  942.     var slash = str.match(/\//g);
  943.     // look for mo/da/year
  944.     var mdy;
  945.     if (!slash) mdy = null;
  946.     else if (slash.length==1) mdy = str.match(/(\d+)\/(\d+)/);
  947.     else if (slash.length==2) mdy = str.match(/(\d+)\/(\d+)\/(\d+)/);
  948.     else {
  949.         //3*/print("too many slashes");
  950.         return bad;
  951.     }
  952.     if (slash && !mdy) {
  953.         //3*/print("found slash but no mdy");
  954.         return bad;
  955.     }
  956.     //3*/print("mdy: "+mdy);
  957.     if (mdy) {
  958.         if (mdy[1].length>2 || mdy[2].length>2) {
  959.             //3*/print("mmm or ddd");
  960.             return bad;
  961.         }
  962.         month = mdy[1] - 1;
  963.         if (month<0 || month>11) {
  964.             //3*/print("month>12");
  965.             return bad;
  966.         }
  967.         day = mdy[2] - 0;
  968.         if (!day || day>31) {
  969.             //3*/print("day>31");
  970.             return bad;
  971.         }
  972.         if (mdy.length>3) {
  973.             yearSpecified = true;
  974.             year = mdy[3] - 0;
  975.             if (year<1000) {
  976.                 //3*/print("year+2000");
  977.                 year += 2000;
  978.             }
  979.         } else {
  980.             year = now.getFullYear();
  981.         }
  982.     } else {
  983.         month = now.getMonth();
  984.         day = now.getDate();
  985.         year = now.getFullYear();
  986.     }
  987.     // determine # colons in input
  988.     var colon = str.match(/:/g);
  989.     // look for hr:mn:sd
  990.     var hms;
  991.     // any bare numbers floating arround the input?
  992.     if (hms = str.match(/\s\d+\s/g)) {
  993.         // if there's a colon or not just one bare number...
  994.         if (colon || hms.length != 1) {
  995.             //3*/print("bare number but not hour");
  996.             return bad;
  997.         }
  998.         // it's an hour
  999.         hms = str.match(/\s(\d+)\s/);
  1000.     }
  1001.     else if (!colon) hms = null;
  1002.     else if (colon.length==1) hms = str.match(/(\d+):(\d+)/);
  1003.     else if (colon.length==2) hms = str.match(/(\d+):(\d+):(\d+)/);
  1004.     else {
  1005.         //3*/print("too many colons");
  1006.         return bad;
  1007.     }
  1008.     if (colon && !hms) {
  1009.         //3*/print("found colon but no hms");
  1010.         return bad;
  1011.     }
  1012.     //3*/print("hms: "+hms);
  1013.     if (hms) {
  1014.         if (mdy) {
  1015.             // the two fields must not be overlapping
  1016.             var first = mdy;    // assume mdy.index<hms.index
  1017.             var diff = hms.index - mdy.index;
  1018.             if (diff<0) {    // assumption was wrong
  1019.                 first = hms;
  1020.                 diff = -diff;
  1021.             }
  1022.             if (first[0].length > diff) {
  1023.                 //3*/print("mdy and hms overlap");
  1024.                 return bad;
  1025.             }
  1026.         }
  1027.         if (hms[1].length>2) {
  1028.             //3*/print("hhh");
  1029.             return bad;
  1030.         }
  1031.         hours = hms[1] - 0;
  1032.         if (hours>23) {
  1033.             //3*/print("hours>23");
  1034.             return bad;
  1035.         }
  1036.         if ((am || pm) && (hours==0 || hours>12)) {
  1037.             //3*/print("bad hour for am or pm");
  1038.             return bad;
  1039.         }
  1040.         if (am) {                
  1041.             if (hours == 12) {
  1042.                 //3*/print("12am");
  1043.                 hours = 0;
  1044.             } 
  1045.         } else if (pm) {
  1046.             if (hours < 12) {
  1047.                 //3*/print("1-11pm");
  1048.                 hours += 12;
  1049.             }
  1050.         }
  1051.         if (colon) {
  1052.             if (hms[2].length!=2) {
  1053.                 //3*/print("mmm or m");
  1054.                 return bad;
  1055.             }
  1056.             minutes = hms[2] - 0;
  1057.             if (minutes>59) {
  1058.                 //3*/print("minutes>59");
  1059.                 return bad;
  1060.             }
  1061.             if (colon.length==2) {
  1062.                 if (hms[3].length!=2) {
  1063.                     //3*/print("sss or s");
  1064.                     return bad;
  1065.                 }
  1066.                 seconds = hms[3] - 0;
  1067.                 if (seconds>59) {
  1068.                     //3*/print("seconds>59");
  1069.                     return bad;
  1070.                 }
  1071.             } else {
  1072.                 seconds = 0;
  1073.             }
  1074.         } else {
  1075.             minutes = seconds = 0;
  1076.         }
  1077.     } else {
  1078.         if (!mdy) {
  1079.             //3*/print("no mdy or hms");
  1080.             return bad;
  1081.         }
  1082.         if (am || pm) {
  1083.             //3*/print("am or pm but no hms");
  1084.             return bad;
  1085.         }
  1086.         hours = minutes = seconds = 0;
  1087.     }
  1088.     var almdate = new Date(year,month,day,hours,minutes,seconds);
  1089.     if (almdate.getMonth() != month) {
  1090.         //3*/print("bad day for month");
  1091.         return bad;
  1092.     }
  1093.     if (almdate.getTime()+1000 < now.getTime()) {
  1094.         //3*/print("now = "+now.toLocaleString());
  1095.         //3*/print("alm = "+almdate.toLocaleString());
  1096. //        if (!am && !pm && hours && hours<12 &&
  1097. //            almdate.getTime()+12*3600000 > now.getTime()
  1098. //        ) {
  1099. //            //3*/print("plus 12 hours");
  1100. //            almdate.setHours(hours+12);
  1101. //        } else
  1102.         if (!mdy) {
  1103.             //3*/print("tomorrow");
  1104.             almdate.setDate(day+1);    // set to tomorrow
  1105.         } else if (!yearSpecified) {
  1106.             //3*/print("next year");
  1107.             almdate.setFullYear(year+1);    // set to next year
  1108.         } else {
  1109.             //3*/print("already past");
  1110.             return bad;
  1111.         }
  1112.     }
  1113.     //3*/print("parse OK");
  1114.     return almdate;        
  1115. }
  1116.  
  1117. function unsetAlarm(stky) {
  1118.  
  1119.     if (pending == stky) {
  1120.         pending = stky.nextpending;
  1121.     } else {
  1122.         for (var p=pending; p&&p.nextpending!=stky; p=p.nextpending) {}
  1123.         if (p) p.nextpending = stky.nextpending;
  1124.     }
  1125.     stky.nextpending = null;
  1126.     stky.alarm = 0;
  1127.     checkAlarms();
  1128. }
  1129.  
  1130. function applyPreferences() {
  1131.  
  1132.     // PAD, WIDGET, & NON-INDIVIDUAL PREFERENCES
  1133.     
  1134.     if (preferences.setFactoryDefaults.value-0 != 0) {
  1135.  
  1136.         preferences.setFactoryDefaults.value = 0;
  1137.  
  1138.         preferences.padColor.value = preferences.padColor.defaultValue;
  1139.         preferences.defaultColorPad.value = preferences.defaultColorPad.defaultValue;
  1140.         preferences.textColor.value = preferences.textColor.defaultValue;
  1141.         preferences.noteColor.value = preferences.noteColor.defaultValue;
  1142.         preferences.borderColor.value = preferences.borderColor.defaultValue;
  1143.         preferences.activeColor.value = preferences.activeColor.defaultValue;
  1144.  
  1145.         if (preferences.setItems.value-0>0) {
  1146.             preferences.font.value = preferences.font.defaultValue;
  1147.         }
  1148.         if (preferences.setItems.value-0>1) {
  1149.             preferences.textSize.value = preferences.textSize.defaultValue;
  1150.         }
  1151.         if (preferences.setItems.value-0>2) {
  1152.             preferences.almSound.value = preferences.almSound.defaultValue;
  1153.             preferences.noteWidth.value = preferences.noteWidth.defaultValue;
  1154.             preferences.noteHeight.value = preferences.noteHeight.defaultValue;
  1155.             preferences.konfabulatorWindowOpacity.value =
  1156.                 preferences.konfabulatorWindowOpacity.defaultValue;
  1157.             preferences.konfabulatorLockWindowPosition.value =
  1158.                 preferences.konfabulatorLockWindowPosition.defaultValue;
  1159.             preferences.konfabulatorWindowLevel.value =
  1160.                 preferences.konfabulatorWindowLevel.defaultValue;
  1161.             preferences.newKey.value = preferences.newKey.defaultValue;
  1162.             preferences.searchKey.value = preferences.searchKey.defaultValue;
  1163.             preferences.hideShowKey.value = preferences.hideShowKey.defaultValue;
  1164.             preferences.deleteKey.value = preferences.deleteKey.defaultValue;
  1165.         }
  1166.     }
  1167.     if (preferences.defaultColorPad.value-0 != 0)
  1168.         padImage.colorize = preferences.noteColor.value;
  1169.     else    padImage.colorize = preferences.padColor.value;
  1170.     
  1171.     if (preferences.setVisible.value-0 != 0) {
  1172.  
  1173.         preferences.setVisible.value = 0;
  1174.  
  1175.         for (id=0; id<sticky.length; id++) {
  1176.             s = sticky[id];
  1177.             if (s == null  || !s.window || !s.window.visible) continue;
  1178.  
  1179.             s.changed = true;
  1180.             s.edit.color = preferences.textColor.value;
  1181.             s.edit.bgColor = preferences.noteColor.value;
  1182.             s.borderColor = preferences.borderColor.value;
  1183.             s.activeColor = preferences.activeColor.value;
  1184.             s.matte.bgColor = s.edit.editable? s.activeColor: s.borderColor;
  1185.  
  1186.             if (preferences.setItems.value-0>0) {
  1187.                 s.edit.font = s.font = preferences.font.value;
  1188.             }
  1189.             if (preferences.setItems.value-0>1) {
  1190.                 s.edit.size = preferences.textSize.value-0;
  1191.             }
  1192.             if (preferences.setItems.value-0>2) {
  1193.             }
  1194.         }
  1195.     }
  1196.     // UNIVERSAL (as yet) PREFERENCES
  1197.  
  1198.     stickyPad.opacity = preferences.konfabulatorWindowOpacity.value;
  1199.     stickyPad.locked = preferences.konfabulatorLockWindowPosition.value;
  1200.     stickyPad.level = preferences.konfabulatorWindowLevel.value;
  1201.  
  1202.     var s;
  1203.     var id;
  1204.     for (id=0; id<sticky.length; id++) {
  1205.         s = sticky[id];
  1206.         if (s == null || !s.window) continue;
  1207.         s.changed = true;
  1208.         s.window.opacity = preferences.konfabulatorWindowOpacity.value;
  1209.         s.window.locked = preferences.konfabulatorLockWindowPosition.value;
  1210.         s.window.level = preferences.konfabulatorWindowLevel.value;
  1211.     }
  1212.     
  1213.     saveall();
  1214.     
  1215.     // HOTKEY ASSIGNMENTS
  1216.     newKey = makeHotKey(newKey,preferences.newKey.value,"new Sticky();");    
  1217.     searchKey = makeHotKey(searchKey,preferences.searchKey.value,"search();");
  1218.     hideShowKey = makeHotKey(hideShowKey,preferences.hideShowKey.value,"hideShowAll();");    
  1219.     deleteKey = makeHotKey(deleteKey,preferences.deleteKey.value,"deleteSticky();");    
  1220. }
  1221.  
  1222. function makeHotKey(existing,value,action) {
  1223.  
  1224.     if (existing && existing.value == value) return existing;
  1225.     if (!value) {
  1226.         if (existing) delete existing.key;
  1227.         return null;
  1228.     }
  1229.     var mykey = new Object();
  1230.     mykey.value = value;
  1231.     mykey.key = new HotKey();
  1232.     var index = value.lastIndexOf("+",value.length-2);
  1233.     if (index > 0) {
  1234.         mykey.key.modifier = value.slice(0,index);
  1235.         mykey.key.key = value.slice(index+1);
  1236.     } else  mykey.key.key = value;
  1237.     mykey.key.onKeyDown = action;
  1238.     return mykey;
  1239.     }
  1240.  
  1241. function stickyPref(id) {
  1242.  
  1243.     var s = sticky[id];
  1244.     var items = Array();
  1245.     
  1246.     items[0] = new FormField();
  1247.     items[0].type = 'color';
  1248.     items[0].title = 'Text Color';
  1249.     items[0].defaultValue =  s.edit.color;
  1250.     
  1251.     items[1] = new FormField();
  1252.     items[1].type = 'color';
  1253.     items[1].title = 'Sticky Color';
  1254.     items[1].defaultValue =  s.edit.bgColor;
  1255.     
  1256.     items[2] = new FormField();
  1257.     items[2].type = 'color';
  1258.     items[2].title = 'Border Color';
  1259.     items[2].defaultValue =  s.borderColor;
  1260.     
  1261.     items[3] = new FormField();
  1262.     items[3].type = 'color';
  1263.     items[3].title = 'Active Color';
  1264.     items[3].defaultValue =  s.activeColor;
  1265.     items[3].description = "Color of the border when in edit mode.  The border will alternate between this color and its normal color when alarming";
  1266.     
  1267.     items[4] = new FormField();
  1268.     items[4].type = 'font';
  1269.     items[4].title = 'Typeface';
  1270.     items[4].defaultValue = s.font;
  1271.     
  1272.     items[5] = new FormField();
  1273.     items[5].title = 'Text Size';
  1274.     items[5].type = 'text';
  1275.     items[5].defaultValue = s.edit.size;
  1276.  
  1277.     items[6] = new FormField();
  1278.     items[6].title = 'Set default values per this sticky.';
  1279.     items[6].type = 'checkbox';
  1280.     items[6].defaultValue = 0;
  1281.     items[6].description = "Use this sticky's colors, typeface, type size, position and size when creating new stickies.";
  1282.     
  1283.     var values = form(items,'Sticky Preferences','Save','Cancel');
  1284.     
  1285.     if (values != null) {
  1286.         //4*/print("values = " + values);
  1287.         s.changed = true;
  1288.         s.edit.color = values[0];
  1289.         s.edit.bgColor = values[1];
  1290.         s.borderColor = values[2];
  1291.         s.activeColor = values[3];
  1292.         s.matte.bgColor = s.edit.editable? s.activeColor: s.borderColor;
  1293.         s.edit.font = s.font = values[4];
  1294.         // OK - why bother with the s.font property?
  1295.         // s.edit.font is the problem:
  1296.         // What goes in does not always come out.
  1297.         // font&style go in, font alone comes out.
  1298.         // how about concatting with s.edit.style?  NG!
  1299.         // the style that goes into font comes out changed.
  1300.         // Using s.font is lots easier and cleaner.
  1301.         var size = values[5]-0;
  1302.         if (!isNaN(size)&&size>0) s.edit.size = size;
  1303.         else alert("Smarter Stickies:\nThe value entered for Text Size was not accepted.\nPlease enter only a number.");
  1304.         
  1305.         if (values[6]-0) {
  1306.             //4*/print("setting defaults from sticky");
  1307.             preferences.noteHoffset.value = s.window.hOffset;
  1308.             preferences.noteVoffset.value = s.window.vOffset;
  1309.             preferences.noteWidth.value = s.window.width;
  1310.             preferences.noteHeight.value = s.window.height;
  1311.             preferences.font.value = s.font;
  1312.             preferences.textSize.value = s.edit.size;
  1313.             preferences.textColor.value = s.edit.color;
  1314.             preferences.noteColor.value = s.edit.bgColor;
  1315.             preferences.borderColor.value = s.borderColor;
  1316.             preferences.activeColor.value = s.activeColor;
  1317.  
  1318.             if (preferences.defaultColorPad.value-0 != 0)
  1319.                 padImage.colorize = s.edit.bgColor;
  1320.         }        
  1321.     }
  1322. }
  1323.  
  1324. function search() {
  1325.  
  1326.     var items = Array();
  1327.         
  1328.     items[0] = new FormField();
  1329.     items[0].title = 'Enter search term:';
  1330.     items[0].type = 'text';
  1331.  
  1332.     items[1] = new FormField();
  1333.     items[1].title = 'Ignore case';
  1334.     items[1].type = 'checkbox';
  1335.     items[1].defaultValue = 1;
  1336.     
  1337.     items[2] = new FormField();
  1338.     items[2].title = 'Use regular expressions';
  1339.     items[2].type = 'checkbox';
  1340.     items[2].defaultValue = 0;
  1341.     
  1342.     items[3] = new FormField();
  1343.     items[3].title = 'Mode:';
  1344.     items[3].type = 'popup';
  1345.     items[3].option = ['Show Matches','Hide Matches','Hide Non-matches','Show Non-matches'];
  1346.     items[3].optionValue = ['0','1','3','2'];
  1347.     items[3].defaultValue = 0;
  1348.     
  1349.     var values = form(items,'Search Stickies','Search','Cancel');
  1350.     
  1351.     if (values) {    
  1352.         var pattern = values[0];
  1353.         if (values[2]-0 == 0) {    // not regular expression - literalize special char
  1354.             pattern = pattern.replace(/([\/\\\.\*\+\?\|\(\)\[\]\{\}])/g,"\\$1");
  1355.         }
  1356.         var attrib = values[1]-0? "i": "";
  1357.         var showMode = ((values[3]-0) & 1 ) == 0;
  1358.         var matchMode = ((values[3]-0) & 2 ) != 0;
  1359.  
  1360.         try {
  1361.             var regex = new RegExp(pattern,attrib);
  1362.             //6*/print("regex = "+regex);
  1363.  
  1364.             var id;
  1365.             for (id=0; id<sticky.length; id++) {
  1366.                 s = sticky[id];
  1367.                 if (s == null || !s.window) continue;
  1368.  
  1369.                 if (regex.test(s.edit.data) ^ matchMode) {
  1370.                     showMode ? show(id) : hide(id);
  1371.                 }
  1372.             }
  1373.         } catch (e) {
  1374.             alert("Smarter Stickies:\nRegular expression error:  "+e.message);
  1375.         }
  1376.     }
  1377.     stickyPad.visible = padShown;
  1378. }
  1379.  
  1380. function wakeup() {
  1381.  
  1382.     var id;
  1383.     var s
  1384.     for (id=0; id<sticky.length; id++) {
  1385.         s = sticky[id];
  1386.         if (s == null || !s.window) continue;
  1387.  
  1388.         if (s.showAtStartup) {
  1389.             show(id);
  1390.         }
  1391.     }
  1392.  
  1393.     checkAlarms();
  1394. }
  1395.  
  1396. function onScreen(s) {        // an attempted patch to recover dissappearing stickies
  1397.  
  1398.     var errors = "";
  1399.  
  1400.     if (s.window.width < MIN_WIDTH) {
  1401.         errors += " width";
  1402.         s.window.width = preferences.noteWidth.value;
  1403.         pack(s);
  1404.     }
  1405.     if (s.window.height < MIN_HEIGHT) {
  1406.         errors += " height";
  1407.         s.window.height = preferences.noteHeight.value;
  1408.         pack(s);
  1409.     }
  1410.     if (s.window.hOffset < screen.availLeft) {
  1411.         errors += " left";
  1412.         s.window.hOffset = screen.availLeft;
  1413.     }
  1414.     if (s.window.vOffset < screen.availTop) {
  1415.         errors += " top";
  1416.  
  1417.         s.window.vOffset = screen.availTop;
  1418.     }
  1419.     var hOverhang = s.window.hOffset + s.window.width
  1420.         - (screen.availLeft + screen.availWidth);
  1421.     if (hOverhang > 0) {
  1422.         errors += " right";
  1423.         s.window.hOffset -= hOverhang;
  1424.     }
  1425.     var vOverhang = s.window.vOffset + s.window.height
  1426.         - (screen.availTop + screen.availHeight);
  1427.     if (vOverhang > 0) {
  1428.         errors += " bottom";
  1429.         s.window.vOffset -= vOverhang;
  1430.     }
  1431.     //7*/if (errors.length) print("onScreen errors: "+errors);
  1432.     // not doing anything with any errors found at this point
  1433.     // width and height are real errors that need to be tracked down.
  1434.     // the others are not really errors if the sticky is not completely off the screen
  1435. }
  1436.  
  1437. function clone(id) {
  1438.  
  1439.     var s = sticky[id];
  1440.     var copy = new Object();
  1441.     
  1442.     copy.showAtStartup = s.showAtStartup;
  1443.     copy.alarm = s.alarm;
  1444.     copy.hOffset = s.window.hOffset + 20;
  1445.     copy.vOffset = s.window.vOffset + 20;
  1446.     copy.width = s.window.width;
  1447.     copy.height = s.window.height;
  1448.     copy.visible = true;
  1449.     copy.font = s.font;
  1450.     copy.size = s.edit.size;
  1451.     copy.textColor = s.edit.color;
  1452.     copy.noteColor = s.edit.bgColor;
  1453.     copy.borderColor = s.borderColor;
  1454.     copy.activeColor = s.activeColor;
  1455.     copy.data = s.edit.data;
  1456.  
  1457.     onScreen(new Sticky(copy));
  1458. }
  1459.  
  1460. function onFocus(id) {
  1461.  
  1462.     focusedID = id;
  1463. }
  1464.  
  1465. function onBlur(id) {
  1466.  
  1467.     save(id);
  1468.     focusedID = null;
  1469. }
  1470.  
  1471. function deleteSticky(id) {
  1472.  
  1473.     if (!id) id = focusedID;
  1474.     var s = sticky[id];
  1475.     if (!id || !s || !s.window || !s.window.visible) {
  1476.         beep();
  1477.         return;
  1478.     }
  1479.     
  1480.     // remove from pending alarm list (if found there)
  1481.     if (s.alarm) unsetAlarm(s);
  1482.     sticky[id] = focusedID = null;
  1483.     if (filesystem.itemExists(filename(id))) {
  1484.         filesystem.moveToRecycleBin(filename(id));
  1485.     }
  1486.     stickycount--;
  1487. }
  1488.  
  1489. function onStickyDrop(id) {
  1490.  
  1491.     var s = sticky[id];
  1492.     s.edit.replaceSelection(system.event.data.slice(1).join("\n"));        
  1493. }
  1494.  
  1495. function onPadDrop() {
  1496.  
  1497.     var s = new Sticky();
  1498.     onStickyDrop(s.id);
  1499. }